#include "defines.h"
#include "lib.h"
#include "kozos.h"
#include "serial.h"
#include "nic.h"
#include "net.h"

#define BUFSIZE 32

static int getline(unsigned char *buf)
{
    int i = 0, c;

    while (1) {
        c = serial_recv_byte(SERIAL_DEFAULT_DEVICE);

        if (c == -1) return -1;
        if (c == '\t') continue; // バックスペースでずれるので無視

        if (c == '\r') {
            putc('\n');
            buf[i++] = '\0';
            break;
        }

        if (c == '\b') {
            if (i > 0) {
                puts("\b \b");
                i--;
            }
        } else if (i < BUFSIZE - 1) {
            putc((unsigned char)c);
            buf[i++] = c;
        }
    }

    return i-1;
}

static void print_IP(uint8 *buf)
{
    putval(buf[0], 0);
    puts(".");
    putval(buf[1], 0);
    puts(".");
    putval(buf[2], 0);
    puts(".");
    putval(buf[3], 0);
}

static char *getarg(char **pp)
{
    char *p = *pp;
    char *q;

    while (*p == ' ') p++;
    if (*p == '\0') return NULL;
    q = p;
    while (*p != ' ' && *p != '\0') p++;
    if (*p == ' ') *p++ = '\0';

    *pp = p;
    return q;
}

unsigned long xtol(const char *s)
{
    unsigned long ret = 0;

    while (*s == ' ')
        s++;
    
    if (*s == '0' && *(s+1) == 'x')
        s += 2;

    for(;;s++) {
        char c = s[0];
        
        if ('0' <= c && c <= '9') {
            ret = (ret << 4) + (c - '0');
        } else if ('A' <= c && c <= 'F') {
            ret = (ret << 4) + (c - 'A' + 10);
        } else if ('a' <= c && c <= 'f') {
            ret = (ret << 4) + (c - 'a' + 10);
        } else {
            break;
        }
    }
    
    return ret;
}

#define FILENAMESIZE 16
#define DEFAULT_NAK_INTERVAL 5
static const uint8 default_src_IP[] = {192, 168, 0, 10};
static const uint8 default_dst_IP[] = {192, 168, 0, 2};
#define DEFAULT_FILENAME "kozos"
#define DEFAULT_TFTP_PORT 10000

int tftptest_main(int argc, char *argv[])
{
    static char buf[BUFSIZE];
    static net_params params;
    static char filename[FILENAMESIZE + 1];

    extern char buffer_start, buffer_end, netbuf_start, buffer_min;

    int nak_interval;
    net_params *np = &params;
    uint8 *nbuf = (uint8 *)&netbuf_start;
    uint8 *buf_sta;

    int i;

    nic_init();
    nic_MAC(np->src_MAC);

    puts("MAC ");
    for (i = 0; i < 6; i++) {
        putxval(np->src_MAC[i], 2);
        puts(i == 5 ? "\n" : ":");
    }

    nak_interval = DEFAULT_NAK_INTERVAL;
    memcpy(np->src_IP, default_src_IP, 4);
    memcpy(np->dst_IP, default_dst_IP, 4);
    strcpy(filename, DEFAULT_FILENAME);
    np->src_port = DEFAULT_TFTP_PORT;
    buf_sta = (uint8 *)&buffer_start;

    while (1) {
        char *p, *q;
        uint32 size;

        puts("tftp> ");

        i = getline(buf);
        if (i == -1) {
            puts("serial error !!\n");
            serial_error_clear(SERIAL_DEFAULT_DEVICE);
            continue;
        }

        p = buf;
        q = getarg(&p);
        if (!q) continue; // 空行はパス

        if (!strcmp(q, "get")) {
            q = getarg(&p);
            if (q) {
                if (strlen(q) <= FILENAMESIZE) {
                    strcpy(filename, q);
                } else {
                    puts("File name is too long.\n");
                    continue;
                }            
            }
        
            if (mac_get(nbuf, np) < 0) {
                puts("ARP timeout.\n");
                continue;
            }

            if (tftp_down(nbuf, np, filename, buf_sta,
                          (uint32)&buffer_end - (uint32)buf_sta, &size)
                    < 0)
            {
                continue; // エラーメッセージはtftp_downで表示
            }

            puts("Transfer completed (" );
            puts(filename);
            puts(", ");
            putval(size, 0);
            puts(" bytes)\n");

        } else if (!strcmp(q, "set")) {
            char *q1, *q2;

            q1 = getarg(&p);
            q2 = getarg(&p);

            if (q2) {
                if (!strcmp(q1, "filename") || !strcmp(q1, "fn")) {
                    if (strlen(q2) <= FILENAMESIZE) {
                        strcpy(filename, q2);
                    } else {
                        puts("File name is too long.\n");
                    }

                } else if (!strcmp(q1, "dst_ip") || !strcmp(q1, "dip")) {
                    if (parse_IP(np->dst_IP, q2) < 0) 
                        puts("input IP error.\n");

                } else if (!strcmp(q1, "src_ip") || !strcmp(q1, "sip")) {
                    if (parse_IP(np->src_IP, q2) < 0)
                        puts("input IP error.\n");

                } else if (!strcmp(q1, "nak_interval") || !strcmp(q1, "ni")) {
                    int i = atoi(q2);
                    if (1 <= i && i <= 26) {
                        nak_interval = i;
                    } else {
                        puts("input value error (1-26)\n");
                    }

                } else if (!strcmp(q1, "tftp_port") || !strcmp(q1, "tp")) {

                    np->src_port = atoi(q2); // オーバーフローしても大丈夫なようだ

                } else if (!strcmp(q1, "buffer_start") || !strcmp(q1, "bs")) {

                    uint8 *x = (uint8 *)xtol(q2);
                    if ((uint8 *)&buffer_min <= x && x <= (uint8 *)&buffer_end) {
                        buf_sta = x;
                    } else {
                        puts("Out of bounds (0x");
                        putxval((uint32)&buffer_min, 6);
                        puts(" - 0x");
                        putxval((uint32)&buffer_end, 6);
                        puts(")\n");
                    }

                } else {
                    puts("unknown parameter.\n");
                }

            } else {
                puts("nak_interval(ni): ");
                putval(nak_interval, 0);
                puts("\n");

                puts("filename(fn): ");
                puts(filename);
                puts("\n");

                puts("src_ip(sip): ");
                print_IP(np->src_IP);
                puts("\n");

                puts("dst_ip(dip): ");
                print_IP(np->dst_IP);
                puts("\n");

                puts("tftp_port(tp): ");
                putval(np->src_port, 0);
                puts("\n");

                puts("buffer_start (bs): 0x");
                putxval((uint32)buf_sta, 6);
                puts("\n");
            }

        } else if (!strcmp(q, "reset")) {

            kz_reset();

        } else {
            puts("unknown command.\n");
        }
    }

    return 0;
}
